﻿using CommunityToolkit.Mvvm.Input;
using HandyControl.Expression.Shapes;
using ImageHandler.Models;
using Microsoft.Win32;
using OpenCvSharp;
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Windows.Media.Imaging;

namespace ImageHandler.ViewModels
{
    /// <summary>
    /// 在图像拍摄或是扫描过程中，获取到不规则的矩形，这样的图像若不预处理，对后期的处理过程中会造成较大的难度
    /// 需要通过透视变换来校正图像，得到正确的形状；
    /// </summary>
    public class E005_AngleTransformationViewModel : PartViewModel
    {
        public E005_AngleTransformationViewModel()
        {
            Header = "透视变换处理";
        }

        public override void Run(string path)
        {
            // 读取图像，图像灰度
            Mat src = Cv2.ImRead(path, ImreadModes.Color);
            if (src.Empty())
            {
                OperateLogAction?.Invoke("无法读取图像！");
                return;
            }
            Result.ReslutInfos.Clear();

            // 转换为灰度图像
            Mat grayMat = new Mat();
            Cv2.CvtColor(src, grayMat, ColorConversionCodes.BGR2GRAY);
            AddMatToResultInfos(grayMat, "原图->灰度图像");

            // 高斯模糊
            Mat gaussianBlurMat = new Mat();
            Cv2.GaussianBlur(grayMat, gaussianBlurMat, new Size(3, 3), 9, 9, BorderTypes.Default);
            AddMatToResultInfos(gaussianBlurMat, "灰度图像->高斯模糊");

            Mat edgesMat = new Mat();
            //Cv2.Canny(blurMat, edgesMat, 50, 50 * 2, 3);
            //Cv2.Laplacian(gaussianBlurMat, edgesMat, MatType.CV_8UC3, 3);
            //AddMatToResult(edgesMat, "高斯模糊->拉普拉斯算子");

            // 二值化
            Mat binaryMat = new Mat();
            Cv2.Threshold(gaussianBlurMat, binaryMat, 0, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
            AddMatToResultInfos(binaryMat, "高斯模糊->二值化");

            // 形态学操作
            Mat morphMat = new Mat();
            Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3), new Point(-1, -1));
            Cv2.MorphologyEx(binaryMat, morphMat, MorphTypes.Close, kernel, new Point(-1, -1), 2);
            AddMatToResultInfos(morphMat, "二值化->形态学闭操作");

            //轮廓发现
            Cv2.BitwiseNot(morphMat, morphMat, new Mat());
            AddMatToResultInfos(morphMat, "二值化->反转");

            // 轮廓检测
            Point[][] contours;
            HierarchyIndex[] hierarchy;
            Cv2.FindContours(morphMat, out contours, out hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple);

            // 轮廓绘制
            int width = src.Cols;
            int height = src.Rows;
            Mat drawImg = Mat.Zeros(src.Size(), MatType.CV_8UC3);
            for (int i = 0; i < contours.Length; i++)
            {
                Rect rect = Cv2.BoundingRect(contours[i]);

                if (rect.Width > width / 4 && rect.Width < width - 10)
                {
                    Cv2.DrawContours(drawImg, contours, i, new Scalar(0, 0, 255), 2, LineTypes.Link8, hierarchy);
                }
            }
            AddMatToResultInfos(drawImg, "轮廓检测");

            //查找直线
            Mat contoursImg = new Mat();
            int accu = Math.Min(width / 2, height / 2);
            Cv2.CvtColor(drawImg, contoursImg, ColorConversionCodes.BGR2GRAY);
            LineSegmentPoint[] lines = Cv2.HoughLinesP(contoursImg, 1, Math.PI / 180.0, accu / 5, accu / 5);
            Mat linesImg = Mat.Zeros(src.Size(), MatType.CV_8UC3);
            for (int i = 0; i < lines.Length; i++)
            {
                Cv2.Line(linesImg, lines[i].P1, lines[i].P2, new Scalar(0, 0, 255), 1, LineTypes.Link8);
            }
            AddMatToResultInfos(linesImg, "直线检测");


            // 寻找与定位上下左右4条线 差异最大化 横线两条线的Y值差别大,竖线X防线差值大
            // 计算四条线的交点
            int deltah = 0, deltaW = 0;
            LineSegmentPoint topLine = new LineSegmentPoint();
            LineSegmentPoint bottomLine = new LineSegmentPoint();
            LineSegmentPoint leftLine = new LineSegmentPoint();
            LineSegmentPoint rightLine = new LineSegmentPoint();
            string str = string.Empty;
            for (int i = 0; i < lines.Length; i++)
            {
                //上下横线 判断两点位置坐标差值 并且长度大于最小边的1/10;
                deltah = Math.Abs(lines[i].P2.X - lines[i].P1.X);
                if (lines[i].P2.Y < (height / 5 * 2) && lines[i].P1.Y < (height / 5 * 2) && deltah > accu / 10)
                {
                    topLine = lines[i];
                    str += "t";
                }

                if (lines[i].P2.Y > height / 5 * 3 && lines[i].P1.Y > height / 5 * 3 && deltah > accu / 10)
                {
                    bottomLine = lines[i];
                    str += "b";
                }

                //宽度 x方向
                deltaW = Math.Abs(lines[i].P2.Y - lines[i].P1.Y);
                if (lines[i].P1.X < (width / 5) && lines[i].P2.X < (width / 5) && deltaW > accu / 10)
                {
                    leftLine = lines[i];
                    str += "l";
                }

                //在x五分之四的位置查找最右边线上的点
                if (lines[i].P1.X > (width / 5 * 4) && lines[i].P2.X > (width / 5 * 4) && deltaW > accu / 10)
                {
                    rightLine = lines[i];
                    str += "r";
                }
            }

            if (!(str.Contains("t") && str.Contains("b") && str.Contains("l") && str.Contains("r")))
            {
                OperateLogAction?.Invoke("没有找到四条线！");
                return;
            }

            //绘制圆点位置
            Cv2.Circle(linesImg, topLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, topLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, bottomLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, bottomLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, leftLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, leftLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, rightLine.P1, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, rightLine.P2, 2, new Scalar(255, 255, 0), 2, LineTypes.Link8, 0);
            AddMatToResultInfos(linesImg, "四条线交点");


            #region 计算透视变换的四个点
            //y=kx+b 求出两条直线的交点 从而求出四个点
            double k1, c1;
            //两点法 (y1-y2)/(x1-x2) = k  //横线X方向差值大 防止Y方向值相同 分母为零
            k1 = Convert.ToDouble(topLine.P2.Y - topLine.P1.Y) / Convert.ToDouble(topLine.P2.X - topLine.P1.X);
            c1 = topLine.P1.Y - k1 * topLine.P1.X;

            double k2, c2;
            k2 = Convert.ToDouble(bottomLine.P2.Y - bottomLine.P1.Y) / Convert.ToDouble(bottomLine.P2.X - bottomLine.P1.X);
            c2 = bottomLine.P1.Y - k2 * bottomLine.P1.X;

            //竖线Y方向差值大 防止X方向值相同 分母为零
            double k3, c3;
            if (Convert.ToDouble(leftLine.P2.X - leftLine.P1.X) == 0)//防止分母为0
            {
                leftLine.P2.X = leftLine.P2.X + 1;
            }
            k3 = Convert.ToDouble(leftLine.P2.Y - leftLine.P1.Y) / Convert.ToDouble(leftLine.P2.X - leftLine.P1.X);
            c3 = leftLine.P1.Y - k3 * leftLine.P1.X;

            double k4, c4; //考虑p1点和p2点x或y坐标相同
            if (Convert.ToDouble(rightLine.P2.X - rightLine.P1.X) == 0)//防止分母为0
            {
                rightLine.P2.X = rightLine.P2.X + 1;
            }
            k4 = Convert.ToDouble(rightLine.P2.Y - rightLine.P1.Y) / Convert.ToDouble(rightLine.P2.X - rightLine.P1.X);
            c4 = rightLine.P1.Y - k4 * rightLine.P1.X;


            //四条直线的焦点
            //左上角
            Point p0 = new Point();
            p0.X = Convert.ToInt32((c1 - c3) / (k3 - k1));
            p0.Y = Convert.ToInt32(k1 * p0.X + c1);

            //右上角
            Point p1 = new Point();
            p1.X = Convert.ToInt32((c1 - c4) / (k4 - k1));
            p1.Y = Convert.ToInt32(k1 * p1.X + c1);

            //左下角
            Point p2 = new Point();
            p2.X = Convert.ToInt32((c2 - c3) / (k3 - k2));
            p2.Y = Convert.ToInt32(k2 * p2.X + c2);

            //右下角
            Point p3 = new Point();
            p3.X = Convert.ToInt32((c2 - c4) / (k4 - k2));
            p3.Y = Convert.ToInt32(k2 * p3.X + c2);

            //绘制圆点位置
            Cv2.Circle(linesImg, p0, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, p1, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, p2, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);
            Cv2.Circle(linesImg, p3, 2, new Scalar(0, 0, 255), 2, LineTypes.Link8, 0);

            Cv2.Line(linesImg, topLine.P1, topLine.P2, new Scalar(255, 0, 0), 1, LineTypes.Link8, 0);
            AddMatToResultInfos(linesImg, "四角");

            //透视变换
            List<Point2f> srcContoursPoints = new List<Point2f>();
            srcContoursPoints.Add(p0);
            srcContoursPoints.Add(p1);
            srcContoursPoints.Add(p2);
            srcContoursPoints.Add(p3);

            List<Point2f> dstContoursPoints = new List<Point2f>();
            dstContoursPoints.Add(new Point2f(0, 0));
            dstContoursPoints.Add(new Point2f(width, 0));
            dstContoursPoints.Add(new Point2f(0, height));
            dstContoursPoints.Add(new Point2f(width, height));

            //透视变换矩阵
            Mat retImg = new Mat();
            Mat warpMat = Cv2.GetPerspectiveTransform(srcContoursPoints, dstContoursPoints);
            Cv2.WarpPerspective(src, retImg, warpMat, retImg.Size(), InterpolationFlags.Linear);
            AddMatToResultInfos(retImg, "透视转换");
            #endregion

        }
    }
}
